import { destroy, getParentOfType, getRoot, isAlive, types } from "mobx-state-tree";

import { guidGenerator } from "../core/Helpers";
import Tree, { TRAVERSE_SKIP } from "../core/Tree";
import Area from "../regions/Area";
import { isDefined } from "../utils/utilities";

const localStorageKeys = {
  order: "relations:order",
};

/**
 * Relation between two different nodes
 */
const Relation = types
  .model("Relation", {
    id: types.optional(types.identifier, guidGenerator),

    node1: types.reference(Area),
    node2: types.reference(Area),

    direction: types.optional(types.enumeration(["left", "right", "bi"]), "right"),

    // labels
    labels: types.maybeNull(types.array(types.string)),
  })
  .volatile(() => ({
    showMeta: false,
    visible: true,
  }))
  .views((self) => ({
    get parent() {
      return getParentOfType(self, RelationStore);
    },

    get control() {
      return self.parent.control;
    },

    get selectedValues() {
      return self.labels?.filter((relationLabel) => {
        return self.control?.values.includes(relationLabel);
      });
    },

    get hasRelations() {
      return self.control?.children?.length > 0;
    },

    get shouldRender() {
      if (!isAlive(self)) return false;
      const { node1: start, node2: end } = self;
      const [sIdx, eIdx] = [start.item_index, end.item_index];

      // as we don't currently have a unified solution for multi-object segmentation
      // and the Image tag is the only one to support it, we rely on its API
      // TODO: make multi-object solution more generic
      if (isDefined(sIdx) && start.object.multiImage && sIdx !== start.object.currentImage) return false;

      if (isDefined(eIdx) && end.object.multiImage && eIdx !== end.object.currentImage) return false;

      return true;
    },
  }))
  .actions((self) => ({
    rotateDirection() {
      const d = ["left", "right", "bi"];
      let idx = d.findIndex((item) => item === self.direction);

      idx = idx + 1;
      if (idx >= d.length) idx = 0;

      self.direction = d[idx];
    },

    toggleHighlight() {
      if (self.node1 === self.node2) {
        self.node1.toggleHighlight();
      } else {
        self.node1.toggleHighlight();
        self.node2.toggleHighlight();
      }
    },

    toggleMeta() {
      self.showMeta = !self.showMeta;
    },

    setSelfHighlight(highlighted = false) {
      if (highlighted) {
        self.parent.setHighlight(self);
      } else {
        self.parent.removeHighlight();
      }
    },

    toggleVisibility() {
      self.visible = !self.visible;
    },

    setRelations(values) {
      self.labels = values;
    },
  }));

const RelationStore = types
  .model("RelationStore", {
    relations: types.array(Relation),
    order: types.optional(
      types.enumeration(["asc", "desc"]),
      window.localStorage.getItem(localStorageKeys.order) ?? "asc",
    ),
  })
  .volatile(() => ({
    showConnections: true,
    _highlighted: null,
    control: null,
  }))
  .views((self) => ({
    get highlighted() {
      return self.relations.find((r) => r.id === self._highlighted);
    },
    get size() {
      return self.relations.length;
    },
    get orderedRelations() {
      if (!self.relations) return [];
      if (self.order === "asc") {
        return self.relations.slice();
      }
      return self.relations.slice().reverse();
    },
    get isAllHidden() {
      return !self.relations.find((rl) => !rl.visible);
    },
    get values() {
      return self.control?.values ?? [];
    },
  }))
  .actions((self) => ({
    afterAttach() {
      const appStore = getRoot(self);

      // find <Relations> tag in the tree
      let relationsTag = null;

      Tree.traverseTree(appStore.annotationStore.root, (node) => {
        if (node.type === "relations") {
          relationsTag = node;
          return TRAVERSE_SKIP;
        }
      });
      self.setControl(relationsTag);
    },
    setControl(relationsTag) {
      self.control = relationsTag;
    },
    findRelations(node1, node2) {
      const id1 = node1.id || node1;
      const id2 = node2?.id || node2;

      if (!id2) {
        return self.relations.filter((rl) => {
          return rl.node1.id === id1 || rl.node2.id === id1;
        });
      }

      return self.relations.filter((rl) => {
        return rl.node1.id === id1 && rl.node2.id === id2;
      });
    },

    nodesRelated(node1, node2) {
      return self.findRelations(node1, node2).length > 0;
    },

    addRelation(node1, node2) {
      if (self.nodesRelated(node1, node2)) return;

      const rl = Relation.create({ node1, node2 });

      // self.relations.unshift(rl);
      self.relations.push(rl);

      return rl;
    },

    deleteRelation(rl) {
      self.relations = self.relations.filter((r) => r.id !== rl.id);
      destroy(rl);
    },

    deleteNodeRelation(node) {
      // lookup $node and delete it's relation
      const rl = self.findRelations(node);

      rl.length && rl.forEach(self.deleteRelation);
    },

    deleteAllRelations() {
      self.relations.forEach((rl) => destroy(rl));
      self.relations = [];
    },

    serialize() {
      return self.relations.map((r) => {
        const s = {
          from_id: r.node1.cleanId,
          to_id: r.node2.cleanId,
          type: "relation",
          direction: r.direction,
        };

        if (r.selectedValues) s.labels = r.selectedValues;

        return s;
      });
    },

    deserializeRelation(node1, node2, direction, labels) {
      const rl = self.addRelation(node1, node2);

      if (!rl) return; // duplicated relation

      rl.direction = direction;
      rl.labels = labels;
    },

    toggleConnections() {
      self.showConnections = !self.showConnections;
    },

    toggleOrder() {
      self.order = self.order === "asc" ? "desc" : "asc";
      window.localStorage.setItem(localStorageKeys.order, self.order);
    },

    toggleAllVisibility() {
      const shouldBeHidden = !self.isAllHidden;

      self.relations.forEach((rl) => {
        if (rl.visible !== shouldBeHidden) {
          rl.toggleVisibility();
        }
      });
    },

    setHighlight(relation) {
      self._highlighted = relation.id;
    },

    removeHighlight() {
      self._highlighted = null;
    },
  }));

export default RelationStore;
